/*
 * $Id: FileManager.java,v 1.8 2009/05/29 16:19:05 afaichun Exp $
 * 
 * Copyright (C) 2008 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.onwatch.util;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.database.dao.IRequestDAO;
import com.ge.healthcare.autosc.common.database.dao.IRequestDefn2OnwatchFileInfoDAO;
import com.ge.healthcare.autosc.common.database.pojo.ActRequest;
import com.ge.healthcare.autosc.common.database.pojo.ActRequestList;
import com.ge.healthcare.autosc.common.database.pojo.CfgOnwatchFileInfo;
import com.ge.healthcare.autosc.common.exceptions.ZipperUtilException;
import com.ge.healthcare.autosc.common.util.ZipperUtil;
import com.ge.healthcare.autosc.onwatch.exception.FileManagerException;
/**
 * This class provides utilities to manipulate and manage files and directories.  
 * @author 212042946
 *
 */
public class FileManager {

	private DirectoryStore jobDirStore = null;
	private static IRequestDAO requestDAO = null;
	private static IRequestDefn2OnwatchFileInfoDAO requestDefn2OnwatchFileInfoDAO = null;
	
	/**
	 * Constructor
	 */
	public FileManager() {}

	/**
	 * Init method (Spring init method)
	 */
	public void init() {
		Assert.notNull(requestDAO, 
		"ActRequestListDAO is not defined. Use setActRequestListDAO to provide one");
		Assert.notNull(requestDefn2OnwatchFileInfoDAO,
			"requestDefn2OnwatchFileInfoDAO is not defined. Use setRequestDefn2OnwatchFileInfoDAO to provide one");
	}
	
	/**
	 * Constructor. Create the batchId level directory
	 * @param batchId
	 */
	public FileManager(Long batchId) {
		batchId = (batchId == null)?0:batchId;
		jobDirStore = new DirectoryStore(ASCUtil.getJobDir(batchId));
	}
	
	/**
	 * Add batchId level directory. This is intended to use when default constructor
	 * is used.
	 * @param batchId
	 * @return
	 */
	public File addBatchDir(Long batchId) {
		jobDirStore = new DirectoryStore(ASCUtil.getJobDir(batchId));
		return jobDirStore.getRootFile();
	}
	
	/**
	 * Get batchId level directory file object
	 * @return
	 */
	public File getBatchDir() {
		File batchDir = null;
		if(jobDirStore != null) {
			batchDir = jobDirStore.getRootFile();
		}
		return batchDir;
	}
	
	/**
	 * Add requestListId level directory under batchId level directory
	 * @param requestListId
	 * @return requestListId directory File object
	 * @throws FileManagerException
	 */
	public File addRequestListDir(Long requestListId) 
		throws FileManagerException {
		String methodName = "addRequestListDir";
		if(jobDirStore == null) {
			String errorStr = "Required to add Batch directory first";
			ASCLogger.error(this.getClass(), methodName, errorStr);
			throw new FileManagerException(errorStr);
		}
		
		File createdDir = jobDirStore.addDirEntry(requestListId.toString());
		if(createdDir == null) {
			String errorStr = "Cannot register RequestListId as directory"; 
			ASCLogger.error(this.getClass(), methodName, errorStr);
			throw new FileManagerException(errorStr);
		}
		return createdDir;
	}
	
	/**
	 * Add request level directory under requestList level directory
	 * @param requestListId
	 * @param requestId
	 * @return
	 * @throws FileManagerException
	 */
	public File addRequestDir(Long requestListId, Long requestId) 
		throws FileManagerException {
		String methodName = "addRequestDir";
		if(jobDirStore == null) {
			String errorStr = "Required to create Batch directory first";
			ASCLogger.error(this.getClass(), methodName, errorStr);
			throw new FileManagerException(errorStr);
		}
		
		File createdDir = jobDirStore.addDirEntry(
				requestListId.toString(), requestId.toString());
		if(createdDir == null) {
			String errorStr = "RequestListId " + requestListId + 
				" is not registered to file manager as directory"; 
			// RequestList directory is not registered.
			ASCLogger.error(this.getClass(), methodName, errorStr);
			throw new FileManagerException(errorStr);
		}
		return createdDir;
	}
	
	/**
	 * Get File object for the requestId directory
	 * @param requestListId
	 * @param requestId
	 * @return
	 * @throws FileManagerException
	 */
	public File getRequestDirFile(Long requestListId, Long requestId) 
		throws FileManagerException {
		String methodName = "getRequestDirFile";
		if(jobDirStore == null) {
			String errorStr = "Required to create Batch directory first";
			ASCLogger.error(this.getClass(), methodName, errorStr);
			throw new FileManagerException(errorStr);
		}
		
		String searchPath = requestListId.toString();
		searchPath = DirectoryStore.addPath(searchPath, requestId.toString());
		File requestFile = jobDirStore.getDirEntry(searchPath);
		if(requestFile == null) {
			String errorStr = "RequestId " + requestId + 
				" is not registered to file manager as directory"; 
			ASCLogger.error(this.getClass(), methodName, errorStr);
			throw new FileManagerException(errorStr);
		}
		return requestFile;
	}
	
	/**
	 * Delete batchId level directory and all its content.
	 */
	public void deleteBatchDir() {
		if(jobDirStore != null) {
			jobDirStore.deleteRootDir();
		}
	}

	/**
	 * @param RequestDefn2onwatchFileInfoDAO
	 *            the RequestDefn2onwatchFileInfoDAO to set
	 */
	public void setRequestDefn2OnwatchFileInfoDAO(IRequestDefn2OnwatchFileInfoDAO onwatchFileInfoDAO) {
		FileManager.requestDefn2OnwatchFileInfoDAO = onwatchFileInfoDAO;
	}
	
	/**
	 * Set ActRequestDAO
	 * @param requestDAO
	 */
	public void setActRequestDAO(IRequestDAO requestDAO) {
		FileManager.requestDAO = requestDAO;
	}
	
	/**
	 * Put the given file into a given output file.
	 * The caller is responsible to close the stream
	 * @param inFile	Multipart file object with input data inputStream
	 * @param outFile	File object for the output file
	 * @throws FileManagerException
	 */
	public static void putFile(MultipartFile inFile, File outFile) 
		throws FileManagerException {
		String methodName = "putFile";
		if(inFile != null) {
			try {
				if(outFile != null) {
					writeToFile(inFile.getInputStream(), outFile);
				} else {
					String errorStr = "output File is undefined.";
					ASCLogger.error(FileManager.class, methodName, errorStr);
					throw new FileManagerException(errorStr);
				}
			} catch (IOException ioe) {
				String errorStr = "Failed opening file stream from input file. File name: " +
					inFile.getOriginalFilename();
				ASCLogger.error(FileManager.class, methodName, errorStr, ioe);
				throw new FileManagerException(errorStr, ioe);
			}
		} 
	}
	
	/**
	 * Unpack file. This assumes the file is in tar.gz format.
	 * @param tarGzipFile
	 * @throws FileManagerException
	 */
	public static void unpackFile(File tarGzipFile) throws FileManagerException {
		String methodName = "unpackFile";
		if(ZipperUtil.isTarGzipFile(tarGzipFile.getName()) == false) {
			String errorStr = "File is not in tar.gz format. File name: " + tarGzipFile.getName();
			ASCLogger.error(FileManager.class, methodName, errorStr);
			throw new FileManagerException(errorStr);
		}
		
		try {
			File destDir = tarGzipFile.getParentFile();
			ZipperUtil.untarGunzipFile(tarGzipFile, destDir);
		} catch (ZipperUtilException ute) {
			String errorStr = "Failed unpacking file or files from the archived format. " +
				"File name: " + tarGzipFile.getName();
			ASCLogger.error(FileManager.class, methodName, errorStr);
			throw new FileManagerException(errorStr);
		}
	}
	
	/**
	 * Get file with the given name from back office.
	 * @param fileName	filename of the getting file
	 * @param requestId	
	 * @return
	 */
	public static File getOnwatchFile(String fileName, Long requestId) {
		String methodName = "getOnwatchFile";
		ActRequest request = requestDAO.getByRequestID(requestId);
		File localFile = null;
		if(request != null) {
			ASCLogger.debug(FileManager.class, methodName, "requset defn Id is " + request.getRequestDefnId());
			List<CfgOnwatchFileInfo> onwatchFileInfoList = 
				requestDefn2OnwatchFileInfoDAO.getOnwatchFileInfoForRequestDefn(
						request.getRequestDefnId());
			for(int cnt=0; cnt < onwatchFileInfoList.size();cnt++) {
				CfgOnwatchFileInfo onwatchFileInfo = onwatchFileInfoList.get(cnt);
				// We only interested in download
				if(onwatchFileInfo.isDownloadToDevice()) {
					File file = new File(onwatchFileInfo.getFileDirectory(),
										onwatchFileInfo.getFileName());
					if(fileName.equals(file.getName())) {
						localFile = file;
						break;
					}
				}
			}
		} else {
			ASCLogger.error(FileManager.class, methodName, 
					"Request not found for request Id " + requestId);
		}
		return localFile;
	}
	
	/**
	 * This method creates standard directories structure for request where
	 * its upload files should be placed.
	 */
	public static FileManager buildFileStructure(ActRequestList requestList) 
		throws FileManagerException {
		String methodName = "buildFileStructure";
		FileManager fileManager = null;
		Long batchId = requestList.getBatchId();
		Long requestListId = requestList.getRequestListId();
		fileManager = new FileManager(batchId);
		ASCLogger.debug(FileManager.class, methodName, "BatchIdDir: " + fileManager.getBatchDir().getPath());		
		try {
			File requestListDir = fileManager.addRequestListDir(requestListId);
			ASCLogger.debug(FileManager.class, methodName, "RequestListDir: " + requestListDir.getPath());
			List<ActRequest> requests = requestList.getRequests();
			for(ActRequest request: requests) {
				File name = fileManager.addRequestDir(requestListId, request.getRequestId());
				ASCLogger.debug(FileManager.class, methodName, "Successfully create dir " + name.getPath());
			}
		} catch (FileManagerException fme) {
			String errorStr = "Failed creating directory for file storage";
			ASCLogger.error(FileManager.class, methodName, errorStr);
			throw new FileManagerException(errorStr, fme);
		}
		return fileManager;
	}

	
	private static void writeToFile(InputStream inputStream, File destFile) 
		throws FileManagerException {
		String methodName = "writeToFile";
		FileOutputStream out = null;
		try {
			DataInputStream in = new DataInputStream(inputStream);
			out = new FileOutputStream(destFile);
			int numberOfBytes = in.available();
			byte bytes[] = new byte[numberOfBytes];
			while(in.read(bytes) != -1) {
				out.write(bytes);
				out.flush();
			}
		} catch (IOException ioe) {
			String errorStr = "Failed to write data to file " + destFile.getPath();
			ASCLogger.error(FileManager.class, methodName, errorStr);
			throw new FileManagerException(errorStr, ioe);
		} finally {
			if(out != null) {
				try {
					out.close();
				} catch( IOException ioe) {
					// Nothing can do
					ASCLogger.warn(FileManager.class, methodName, "Unable to close file " +
							destFile.getPath() + ", continue...");
				}
			}
		}
	}
}
